#include <Question.hpp>
#include <QStringList>
#include <fstream>
#include <cerrno>
#include <iostream>
#include <ctime>

namespace gesturewei{
static struct ___TRIMHELPER{
    bool operator()(int ch)const{
        return !isspace(ch);
    }
}__trimhelper;

static inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), __trimhelper));
}

// trim from end (in place)
static inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),__trimhelper).base(), s.end());
}

// trim from both ends (in place)
static inline void trim(std::string &s) {
    ltrim(s);
    rtrim(s);
}
}

//// trim from start (copying)
//static inline std::string ltrim_copy(std::string s) {
//    ltrim(s);
//    return s;
//}

//// trim from end (copying)
//static inline std::string rtrim_copy(std::string s) {
//    rtrim(s);
//    return s;
//}

//// trim from both ends (copying)
//static inline std::string trim_copy(std::string s) {
//    trim(s);
//    return s;
//}

RandomQuestion::RandomQuestion():rand_device(time(0)),idgenerator(0)
{
    memset(choiceQuestionsData,0,sizeof(choiceQuestionsData));
    memset(judgementQuestionsData,0,sizeof(judgementQuestionsData));
    memset(blankQuestionsData,0,sizeof(blankQuestionsData));
}

RandomQuestion::~RandomQuestion()
{
    for(int i=0;i<UORD_MAP_SIZE;i++){
        if(choiceQuestionsData[i].value){
            choiceQuestionsData[i].value->~StaticChoiceQuestion();
            ChoiceMapNode *node=choiceQuestionsData[i].next;
            for(;node;){
                ChoiceMapNode *next=node->next;
                if (node->value){
                    node->value->~StaticChoiceQuestion();
                }
                free(node);
                node=next;
            }
        }
        if (judgementQuestionsData[i].value){
            judgementQuestionsData[i].value->~StaticJudgementQuestion();
            JudgementMapNode *node=judgementQuestionsData[i].next;
            while(node){
                JudgementMapNode *next=node->next;
                if(node->value){
                    node->value->~StaticJudgementQuestion();
                }
                free(node);
                node=next;
            }
        }
        if (blankQuestionsData[i].value){
            blankQuestionsData[i].value->~StaticBlankQuestion();
            BlankMapNode *node=blankQuestionsData[i].next;
            while(node){
                BlankMapNode *next=node->next;
                if(node->value){
                    node->value->~StaticBlankQuestion();
                }
                free(node);
                node=next;
            }
        }
    }
}

#include <QMessageBox>

int RandomQuestion::addQuestion(const char *fname)
{
    std::ifstream in(fname);
    if (!in){
        return -1;
    }
    std::string type;
    std::getline(in,type); gesturewei::trim(type);
    if (type=="choice"){
        return addChoiceQuestionLib(&in);
    }else if(type=="judgement"){
        return addJudgementQuestionLib(&in);
    }else if(type=="blank"){
        return addBlankQuestionLib(&in);
    }else if(strcmp(type.c_str(),"challenge")==0){
        return addChallengeQuestionLib(&in);
    }else if(strcmp(type.c_str(),"short-answer")==0){
        return addShortAnswerQuestionLib(&in);
    }
    return 0;
}

static void unordermap_insert1(ChoiceMapNode *node,unsigned key,unsigned hash,StaticChoiceQuestion **pout){
    node+=hash;
    if (node->value==NULL){
        *pout=node->value=(StaticChoiceQuestion*)node->buffer;
    }else{
        while(1){
            if (node->value->id==key){
                node->value->~StaticChoiceQuestion();
                *pout=node->value;
                return;
            }
            if (!node->next){
                break;
            }
            node=node->next;
        }
        ChoiceMapNode *tmp=(ChoiceMapNode*)malloc(sizeof(ChoiceMapNode));
        tmp->next=NULL;
        *pout=tmp->value=(StaticChoiceQuestion*)tmp->buffer;
        node->next=tmp;
    }
}

int RandomQuestion::addChoiceQuestionLib(std::istream *in)
{
    while(1){
        std::string ctline,opline;
        std::getline(*in,ctline);
        if (in->eof()){
            break;
        }
        std::getline(*in,opline);
        if (in->eof()){
            break;
        }
        gesturewei::trim(ctline);
        gesturewei::trim(opline);
        unsigned firstSpaceChar;//=opline.find(' ');
        for(firstSpaceChar=0;firstSpaceChar<opline.size();firstSpaceChar++){
            if (isblank(opline.at(firstSpaceChar))){
                break;
            }
        }
        unsigned lastSpaceChar;
        unsigned answer=0;
        for(lastSpaceChar=firstSpaceChar+1;lastSpaceChar<opline.size();lastSpaceChar++){
            char ch=opline.at(lastSpaceChar);
            if (isupper(ch)){
                ch-='A';
            }else if(islower(ch)){
                ch-='a';
            }else{
                continue;
            }
            answer|=(1<<ch);
        }
        unsigned curid=idgenerator++;
        StaticChoiceQuestion *pq;
        unordermap_insert1(choiceQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&pq);
        new (pq) StaticChoiceQuestion(ctline.c_str(),answer,curid);
        size_t colon,last=0;
        while(1){
            colon=opline.find_first_of(';',last);
            pq->options.push_back(QString::fromUtf8(opline.c_str()+last,colon==std::string::npos?firstSpaceChar-last:colon-last));
            if (colon==std::string::npos){
                break;
            }
            last=colon+1;
        }
        choiceQuestions.push_back(curid);
    }
    return 0;
}

static void unordermap_insert2(JudgementMapNode *node,unsigned key, unsigned hash, StaticJudgementQuestion **pout){
    node+=hash;
    if (node->value==NULL){
        *pout=node->value=(StaticJudgementQuestion*)node->buffer;
    }else{
        while(1){
            if (node->value->id==key){
                node->value->~StaticJudgementQuestion();
                *pout=node->value;
                return;
            }
            if (!node->next){
                break;
            }
            node=node->next;
        }
        JudgementMapNode *tmp=(JudgementMapNode*)malloc(sizeof(JudgementMapNode));
        tmp->next=NULL;
        *pout=tmp->value=(StaticJudgementQuestion*)tmp->buffer;
        node->next=tmp;
    }
}

int RandomQuestion::addJudgementQuestionLib(std::istream *in)
{
    std::string ctline,opline;
    while(1){
        std::getline(*in,ctline);
        if (in->eof()){
            break;
        }
        std::getline(*in,opline);
        if(in->eof()){
            break;
        }
        gesturewei::trim(ctline);
        gesturewei::trim(opline);
        unsigned opval=strtoul(opline.c_str(),NULL,10);
        unsigned curid=idgenerator++;
        StaticJudgementQuestion *pq;
        unordermap_insert2(judgementQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&pq);
        new (pq) StaticJudgementQuestion(ctline.c_str(),opval,curid);
        judgementQuestions.push_back(curid);
    }
    return 0;
}

static void unordermap_insert3(BlankMapNode *node, unsigned key,unsigned hash, StaticBlankQuestion **pout){
    node+=hash;
    if (node->value==NULL){
        *pout=node->value=(StaticBlankQuestion*)node->buffer;
    }else{
        while(1){
            if (node->value->id==key){
                node->value->~StaticBlankQuestion();
                *pout=node->value;
                return;
            }
            if (!node->next){
                break;
            }
            node=node->next;
        }
        BlankMapNode *tmp=(BlankMapNode*)malloc(sizeof(BlankMapNode));
        tmp->next=NULL;
        *pout=tmp->value=(StaticBlankQuestion*)tmp->buffer;
        node->next=tmp;
    }
}

static void escape_input(std::string &str){
    for(unsigned i=0;i<str.size();i++){
        char ch=str.at(i);
        if (ch=='\\'){
            if (i+1<str.size()){
                switch(str.at(i+1)){
                case 'n':
                    str.replace(i++,2,1,'\n');
                    break;
                case 't':
                    str.replace(i++,2,1,'\t');
                    break;
                case '\\':
                    str.replace(i++,2,1,'\\');
                    break;
                case 'f':
                    str.replace(i++,2,1,'\f');
                    break;
                case 'r':
                    str.replace(i++,2,1,'\r');
                    break;
                }
            }
        }
    }
}

int RandomQuestion::addBlankQuestionLib(std::istream *in)
{
    std::string ctline,opline;
    while(1){
        std::getline(*in,ctline);
        if (in->eof()){
            break;
        }
        std::getline(*in,opline);
        if (in->eof()){
            break;
        }
        gesturewei::trim(ctline);
        gesturewei::trim(opline);
        escape_input(opline);
        StaticBlankQuestion *pq;
        unsigned curid=idgenerator++;
        unordermap_insert3(blankQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&pq);
        new (pq) StaticBlankQuestion(ctline.c_str(),opline.c_str(),curid);
        blankQuestions.push_back(curid);
    }
    return 0;
}

int RandomQuestion::addChallengeQuestionLib(std::istream *in)
{
    std::string ctline,opline;
    while(1){
        std::getline(*in,ctline);
        if (in->eof()){
            break;
        }
        std::getline(*in,opline);
        if (in->eof()){
            break;
        }
        gesturewei::trim(ctline);
        gesturewei::trim(opline);
        escape_input(opline);
        StaticBlankQuestion *pq;
        unsigned curid=idgenerator++;
        unordermap_insert3(blankQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&pq);
        new (pq) StaticBlankQuestion(ctline.c_str(),opline.c_str(),curid);
        challengeQuestions.push_back(curid);
    }
    return 0;
}

int RandomQuestion::addShortAnswerQuestionLib(std::istream *is)
{
    std::string ctline,opline;
    while(1){
        std::getline(*is,ctline);
        if(is->eof()){
            break;
        }
        std::getline(*is,opline);
        if(is->eof()){
            break;
        }
        gesturewei::trim(ctline);
        gesturewei::trim(opline);
        escape_input(opline);
        StaticBlankQuestion *pq;
        unsigned curid=idgenerator++;
        unordermap_insert3(blankQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&pq);
        new (pq) StaticBlankQuestion(ctline.c_str(),opline.c_str(),curid);
        shortAnswerQuestions.push_back(curid);
    }
    return 0;
}

static void unordermap_find1(ChoiceMapNode *node,unsigned key,unsigned hash,StaticChoiceQuestion** pout){
    node+=hash;
    while (node){
        if (node->value && node->value->id==key){
            *pout=node->value;
            return;
        }
        node=node->next;
    }
}

PStaticChoiceQuestion RandomQuestion::randomChooseChoiceQuestion()
{
    unsigned sz=choiceQuestions.size();
    if (sz==0){
        return NULL;
    }
    unsigned where=rand_device()%sz;
    unsigned curid=choiceQuestions.at(where);
    StaticChoiceQuestion *ret;
    unordermap_find1(choiceQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&ret);
    //choiceQuestions.erase(choiceQuestions.begin()+where);
    std::vector<unsigned>::iterator whereiter=choiceQuestions.begin()+where;
    std::vector<unsigned>::iterator lastiter=choiceQuestions.end()-1;
    if (whereiter!=lastiter){
        *whereiter=*lastiter;
    }
    choiceQuestions.pop_back();
    return ret;
}

static void unordermap_find2(JudgementMapNode *node,unsigned key,unsigned hash,StaticJudgementQuestion** pout){
    node+=hash;
    while (node){
        if (node->value && node->value->id==key){
            *pout=node->value;
            return;
        }
        node=node->next;
    }
}

PStaticJudgementQuestion RandomQuestion::randomChooseJudgementQuestion()
{
    if (judgementQuestions.size()==0){
        return NULL;
    }
    unsigned where=rand_device()%judgementQuestions.size();
    unsigned curid=judgementQuestions.at(where);
    StaticJudgementQuestion *ret;
    unordermap_find2(judgementQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&ret);
    //judgementQuestions.erase(judgementQuestions.begin()+where);
    std::vector<unsigned>::iterator whereiter=judgementQuestions.begin()+where;
    std::vector<unsigned>::iterator lastiter=judgementQuestions.end()-1;
    if (whereiter!=lastiter){
        *whereiter=*lastiter;
    }
    judgementQuestions.pop_back();
    return ret;
}

static void unordermap_find3(BlankMapNode *node, unsigned key, unsigned hash, StaticBlankQuestion **pout){
    node+=hash;
    while (node){
        if (node->value && node->value->id==key){
            *pout=node->value;
            return;
        }
        node=node->next;
    }
}

StaticBlankQuestion *RandomQuestion::randomChooseBlankQuestion()
{
    unsigned sz=blankQuestions.size();
    if (sz==0){
        return NULL;
    }
    sz=rand_device()%sz;
    unsigned curid=blankQuestions.at(sz);
    StaticBlankQuestion *ret;
    unordermap_find3(blankQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&ret);
    //blankQuestions.erase(blankQuestions.begin()+sz);
    std::vector<unsigned>::iterator whereiter=blankQuestions.begin()+sz;
    std::vector<unsigned>::iterator lastiter=blankQuestions.end()-1;
    if (whereiter!=lastiter){
        *whereiter=*lastiter;
    }
    blankQuestions.pop_back();
    return ret;
}

StaticBlankQuestion *RandomQuestion::randomChallengeQuestion()
{
    unsigned sz=challengeQuestions.size();
    if (sz==0){
        return NULL;
    }
    sz=rand_device()%sz;
    unsigned curid=challengeQuestions.at(sz);
    StaticBlankQuestion *ret;
    unordermap_find3(blankQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&ret);
    std::vector<unsigned>::iterator whereiter=challengeQuestions.begin()+sz;
    std::vector<unsigned>::iterator lastiter=challengeQuestions.end()-1;
    if (whereiter!=lastiter){
        *whereiter=*lastiter;
    }
    challengeQuestions.pop_back();
    return ret;
}

StaticBlankQuestion *RandomQuestion::randomShortAnswerQuestion()
{
    unsigned sz=shortAnswerQuestions.size();
    if(sz==0){
        return NULL;
    }
    sz=rand_device()%sz;
    unsigned curid=shortAnswerQuestions.at(sz);
    StaticBlankQuestion *ret;
    unordermap_find3(blankQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&ret);
    std::vector<unsigned>::iterator whereiter=shortAnswerQuestions.begin()+sz;
    std::vector<unsigned>::iterator lastiter=shortAnswerQuestions.end()-1;
    if (whereiter!=lastiter){
        *whereiter=*lastiter;
    }
    shortAnswerQuestions.pop_back();
    return ret;
}

void RandomQuestion::outputChoiceQuestions(std::ostream &ostrm,bool withid)
{
    if (choiceQuestions.size()){
        ostrm<<"choice\n";
        for(unsigned i=0;i<choiceQuestions.size();i++){
            StaticChoiceQuestion *ret;
            unsigned curid=choiceQuestions.at(i);
            unordermap_find1(choiceQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&ret);
            if (withid){
                ostrm<<ret->id<<'\t';
            }
            ostrm<<(ret->content.toUtf8().data())<<'\n';
            //std::stringstream sstrm;
            for(int j=0;j<ret->options.size();j++){
                if (j!=0){
                    ostrm<<(';');
                }
                ostrm<<(ret->options.at(j).toUtf8().data());
            }
            ostrm<<(' ');
            unsigned answer=ret->answer;
            char ch='A';
            for(;answer;){
                if (answer & 1){
                    ostrm<<(ch);
                }
                answer>>=1;
                ch++;
            }
            ostrm<<('\n');
        }
    }
}

void RandomQuestion::outputJudgementQuestions(std::ostream &ostrm,bool withid)
{
    if (judgementQuestions.size()){
        ostrm<<"judgement\n";
        for(unsigned i=0;i<judgementQuestions.size();i++){
            StaticJudgementQuestion *ret;
            unsigned curid=judgementQuestions.at(i);
            unordermap_find2(judgementQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&ret);
            if (withid){
                ostrm<<ret->id<<'\t';
            }
            ostrm<<ret->content.toUtf8().data()<<'\n';
            ostrm<<ret->answer<<'\n';
        }
    }
}

static void deescape_char(std::string &out,const char *content,unsigned size){
    for(unsigned k=0;k<size;k++){
        switch(content[k]){
        case '\n':
            out.append("\\n");
            break;
        case '\\':
            out.append("\\\\");
            break;
        case '\f':
            out.append("\\f");
            break;
        case '\t':
            out.append("\\t");
            break;
        case '\r':
            out.append("\\r");
            break;
        default:
            out.push_back(content[k]);
        }
    }
}

void RandomQuestion::outputBlankQuestions(std::ostream &ostrm,bool withid)
{
    if (blankQuestions.size()){
        ostrm<<"blank\n";
        for(unsigned i=0;i<blankQuestions.size();i++){
            StaticBlankQuestion *ret;
            unsigned curid=blankQuestions.at(i);
            unordermap_find3(blankQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&ret);
            if (withid){
                ostrm<<ret->id<<'\t';
            }
            QByteArray content=ret->content.toUtf8();
            std::string out; out.reserve(content.size()+1);
            deescape_char(out,content.data(),content.size());
            out.push_back('\n');
            ostrm<<out;
            content=ret->answer.toUtf8();
            out.clear(); out.reserve(content.size()+1);
            deescape_char(out,content.data(),content.size());
            out.push_back('\n');
            ostrm<<out;
        }
    }
}

void RandomQuestion::outputChallengeQuestions(std::ostream &ostrm,bool withid)
{
    if (challengeQuestions.size()){
        ostrm<<"challenge\n";
        for(unsigned i=0;i<challengeQuestions.size();i++){
            StaticBlankQuestion *ret;
            unsigned curid=challengeQuestions.at(i);
            unordermap_find3(blankQuestionsData,curid,curid&(UORD_MAP_SIZE-1),&ret);
            if (withid){
                ostrm<<ret->id<<'\t';
            }
            QByteArray content=ret->content.toUtf8();
            std::string out; out.reserve(content.size()+1);
            deescape_char(out,content.data(),content.size());
            out.push_back('\n');
            ostrm<<out;
            content=ret->answer.toUtf8();
            out.clear(); out.reserve(content.size()+1);
            deescape_char(out,content.data(),content.size());
            out.push_back('\n');
            ostrm<<out;
        }
    }
}
